//Andrew Villadsen

import javax.vecmath.Point3d;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.Random;
import java.util.Set;


public class Field
{
	//Double precision constant
	public static final double epsilon = 1e-14;
	
	//Enumeration constants
	public static final int HOME = 0; //represents home side and home team
	public static final int AWAY = 1; //represents home side and away team
	public static final int BOTHTEAMS = 2;
	
	private Team awayTeam; //not player's team
	private Team homeTeam; //player's team
	private Random r = new Random();
	private double test;
	private boolean flag = false;
	private Player thrower = null;
	public Player playerWithDisc = null;
	
	Disc disc; //The disc
	
	private int scored = -1; //indicates which team, if any, has scored in the previous step
	
	//Bounds for the field in the x and y dimensions. 
	//The field is a right rectangle with the upper left corner being (0,0) and the lower right being (boundX, boundY).
	public static final Point3d bounds = new Point3d(100., 50., 50.);
	public static final double GOAL = 10;
	public static final double dt = .01; //step interval, in seconds
	
	public Field(Disc d, Team h, Team a, Player pwd)
	{
		disc = d;
		homeTeam = h;
		awayTeam = a;
		playerWithDisc = pwd;
	}
		
	/***Team Methods***/
	
	public Team getHomeTeam()
	{
		return homeTeam;
	}
	
	public Team getAwayTeam()
	{
		return awayTeam;
	}
	
	//Returns the team the player is on, or null if the player is not on a team.
	public Team getTeam(Player player)
	{
		if(homeTeam.isPlayerOnTeam(player))
			return homeTeam;
		else if(awayTeam.isPlayerOnTeam(player))
			return awayTeam;
		else
			return null;
	}
	
	//Returns an ArrayList of players ordered by x, least to greatest.
	//teamflag indicates which teams are considered by the function; must be HOME, AWAY, or BOTHTEAMS.
	public ArrayList<Player> playersByX(int teamflag)
	{
		Set<Player> consideredPlayers = new HashSet<Player>();
		if(teamflag == HOME)
			consideredPlayers.addAll(homeTeam.getPlayers());
		else if(teamflag == AWAY)
			consideredPlayers.addAll(awayTeam.getPlayers());
		else if(teamflag == BOTHTEAMS)
		{
			consideredPlayers.addAll(homeTeam.getPlayers());
			consideredPlayers.addAll(awayTeam.getPlayers());
		}
		
		ArrayList<Player> orderedPlayers = new ArrayList<Player>();
		double min = bounds.x + epsilon;
		
		Player temp;
		while(consideredPlayers.size() > 0)
		{
			temp = null;
			for(Player player : consideredPlayers)
				if(player.getPosition().x < min + epsilon)
				{
					temp = player;
					min = player.getPosition().x;
				}
			consideredPlayers.remove(temp);
			orderedPlayers.add(temp);
		}
		return orderedPlayers;
	}
	
	//Returns the player closest to a point
	public Player closestTo(Point3d point, Set<Player> players)
	{
		Player closest = null;
		for(Player player : players)
		{
			if(closest == null || player.position.distance(point) < closest.position.distance(point))
				closest = player;
		}
		return closest;
	}
	
	//Returns an ordered list of players where a lower index means the player is closer to the point.
	public ArrayList<Player> playersByClosestTo(Point3d point, Set<Player> players)
	{
		Set<Player> consideredPlayers = new HashSet<Player>(players);
		ArrayList<Player> orderedPlayers = new ArrayList<Player>();
		
		Player temp;
		while(consideredPlayers.size() > 0)
		{
			temp = closestTo(point, consideredPlayers);
			consideredPlayers.remove(temp);
			orderedPlayers.add(temp);
		}
		return orderedPlayers;
	}
	
	/***End Team Methods***/
	
	//Calls step on the disc and all players on the field.
	public void step()
	{
		disc.step();
		Set<Player> players = new HashSet<Player>();
		players.addAll(homeTeam.getPlayers());
		players.addAll(awayTeam.getPlayers());
		if(disc.isHeld() && flag)
		{
			if(isGoal(playerWithDisc))
				goal(playerWithDisc);
			homeTeam.discChange(playerWithDisc);
			awayTeam.discChange(playerWithDisc);
			thrower = playerWithDisc;
			playerWithDisc = null;
		}//End else
		for(Player player : players)
		{
			player.step();
			if(!disc.isHeld() && Math.hypot(disc.position.x - player.position.x, disc.position.y - player.position.y) < player.radius)
			{
				test = r.nextDouble();
				if(test<.05 && Math.hypot(player.position.x-thrower.position.x, player.position.y-thrower.position.y) <= 2)
				{
					if(player.routine instanceof UserMovementRoutine)
						((UserMovementRoutine) player.routine).caughtDisc(disc);
					else
						((PlayerRoutine) player.routine).caughtDisc(disc);
					playerWithDisc = player;
					disc.position = (Point3d) player.position.clone();
					((DiscMechanics) disc.routine).catchDisc();
					homeTeam.discChange(player);
					awayTeam.discChange(player);
					thrower = null;
				}
			}
		}
	}
	
	//Returns true if the player has scored a goal.
	private boolean isGoal(Player player)
	{
		return (player.position.x < Field.GOAL && awayTeam.isPlayerOnTeam(player)) || (player.position.x > bounds.x - Field.GOAL && homeTeam.isPlayerOnTeam(player));
	}
	
	//Modifies scoring.
	private void goal(Player player)
	{
		if(homeTeam.isPlayerOnTeam(player))
			scored = Field.HOME;
		else if(awayTeam.isPlayerOnTeam(player))
			scored = Field.AWAY;
	}
	
	//Indicates which team has scored, or -1 if no team has scored, and resets indicator to -1.
	public int scoringTeam()
	{
		int temp = scored;
		scored = -1;
		return temp;
	}
}